package com.google.jessewilson.codesearch.intellij;

import com.google.jessewilson.codesearch.api.Line;
import com.google.jessewilson.codesearch.api.QueryRunner;
import com.google.jessewilson.codesearch.api.QueryResponse;
import com.google.jessewilson.codesearch.ui.ResultActionsProvider;
import com.google.jessewilson.codesearch.ui.ResultsPanelContext;
import com.google.jessewilson.codesearch.util.EventListEdtProcessor;
import com.google.jessewilson.codesearch.util.Resource;
import com.google.inject.Inject;
import com.intellij.openapi.project.Project;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * A docked search results window that lives in a single IntelliJ project.
 *
 * @author jessewilson
 */
class MultipleResultsPanel extends JPanel {

  private final ResultsPanel.Factory resultsPanelFactory;

  // provide services to the inner panels
  private final IntellijResultsPanelContext context;

  // lazily created on search
  private final JTabbedPane tabs;

  // listeners to the tab structure changes
  private List<Listener> listeners = new ArrayList<Listener>();

  @Inject
  public MultipleResultsPanel(Project project,
      ResultActionsProvider resultActionsProvider,
      ResultsPanel.Factory resultsPanelFactory) {
    super(new BorderLayout());
    this.resultsPanelFactory = resultsPanelFactory;

    context = new IntellijResultsPanelContext(resultActionsProvider, project);

    tabs = new JTabbedPane();
    tabs.setTabPlacement(JTabbedPane.BOTTOM);
    tabs.addMouseListener(new TabMouseListener());

    add(tabs, BorderLayout.CENTER);
  }


  /**
   * Close tab at the action's point.
   */
  private void closeTabAtPoint(Point point) {
    int tab = tabs.indexAtLocation(point.x, point.y);
    if(tab < 0 || tab >= tabs.getTabCount()) {
      return;
    }

    ResultsPanel resultsPanel = (ResultsPanel) tabs.getComponent(tab);
    context.close(resultsPanel);
  }

  /**
   * Listen for changes to the tabs.
   */
  public void addListener(Listener listener) {
    this.listeners = new ArrayList<Listener>(listeners);
    this.listeners.add(listener);
  }
  public void removeListener(Listener listener) {
    this.listeners = new ArrayList<Listener>(listeners);
    for(Iterator<Listener> i = listeners.iterator(); i.hasNext(); ) {
      if(i.next() == listener) {
        i.remove();
        return;
      }
    }
    throw new IllegalArgumentException("No such listener, " + listener);
  }
  public void notifyListeners() {
    for(Listener listener : listeners) {
      listener.tabsChanged();
    }
  }

  public void addResults(QueryRunner queryEditorContext, QueryResponse results) {
    context.openResults(queryEditorContext, results);
  }

  public int getTabCount() {
    return tabs.getTabCount();
  }

  /**
   * Services for the inner panels.
   */
  private class IntellijResultsPanelContext implements ResultsPanelContext<ResultsPanel>, Resource.User {
    private final ResultActionsProvider resultActionsProvider;
    private final EventListEdtProcessor<Line> eventListEdtProcessor;

    public IntellijResultsPanelContext(
        ResultActionsProvider resultActionsProvider,
        Project project) {
      this.resultActionsProvider = resultActionsProvider;
      this.eventListEdtProcessor = new EventListEdtProcessor<Line>(new ProjectFilesFinder(project));
    }

    public ResultActionsProvider getResultActionsProvider() {
      return resultActionsProvider;
    }

    public void openResults(QueryRunner queryEditorContext, QueryResponse results) {
      // register a listener
      results.handle().reserve(this);

      // change the display in IntelliJ so the local files are rendered differently
      eventListEdtProcessor.install(results.getLines());

      // create a new viewer for these results
      ResultsPanel newTab = resultsPanelFactory.create(this, results);

      // add it to the tabs
      tabs.add(results.getShortDescription(), newTab);
      tabs.setSelectedComponent(newTab);

      // notify listeners of the changes
      notifyListeners();
    }

    public void close(ResultsPanel source) {
      // remove the tabs
      tabs.remove(source);

      // stop handling changes on the EDT
      eventListEdtProcessor.uninstall(source.getResults().getLines());

      // dispose the viewer
      source.dispose();

      // deregister the listener
      source.getResults().handle().releaseAndDisposeIfUnused(this);

      // notify listeners of the changes
      notifyListeners();
    }

    public List<QueryResponse> getAllResults() {
      List<QueryResponse> allResults = new ArrayList<QueryResponse>();
      for(int t = 0; t < tabs.getTabCount(); t++) {
        ResultsPanel resultPane = (ResultsPanel)tabs.getComponent(t);
        allResults.add(resultPane.getResults());
      }
      return allResults;
    }
  }

  /**
   * Listener to be notified when the list of tabs changes.
   */
  public interface Listener {
    void tabsChanged();
  }

  /**
   * Listen to clicks on tabs to show a 'close' popup option.
   */
  private class TabMouseListener extends MouseAdapter {
    public void mouseReleased(final MouseEvent mouseEvent) {
      if(mouseEvent.isPopupTrigger() || (mouseEvent.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) {
        Action closeAction = new AbstractAction("Close") {
          public void actionPerformed(ActionEvent e) {
            closeTabAtPoint(mouseEvent.getPoint());
          }
        };
        JPopupMenu popupMenu = new JPopupMenu();
        popupMenu.add(new JMenuItem(closeAction));
        popupMenu.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
      }
    }

    public void mouseClicked(MouseEvent mouseEvent) {
      if(mouseEvent.getButton() == MouseEvent.BUTTON2) {
        closeTabAtPoint(mouseEvent.getPoint());
        return;
      }
    }
  }
}
